/* 
 * Copyright (C) 2001-2003 Jacek Sieka, j_s@telia.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "stdinc.h"
#include "DCPlusPlus.h"

#include "Client.h"

#include "ResourceManager.h"
#include "ClientManager.h"
#include "SearchManager.h"
#include "ShareManager.h"
#include "UploadManager.h"
// CDM EXTENSION BEGINS
#include "ClientProfileManager.h"
#include "DebugManager.h"
#include "QueueManager.h"
//Sulan
#include "HubManager.h"
// CDM EXTENSION ENDS

#include "Socket.h"
#include "UserCommand.h"
#include "StringTokenizer.h"

Client::Counts Client::counts;

Client::Client() : supportFlags(0), userInfo(true), 
	registered(false), firstHello(true), state(STATE_CONNECT), 
	socket(BufferedSocket::getSocket('|')), lastActivity(GET_TICK()), 
	countType(COUNT_UNCOUNTED), reconnect(true), lastUpdate(0)
	, disableSearchSelect(false)
	// CDM EXTENSION BEGINS
	, showIPs(BOOLSETTING(SHOW_IPS))
	// CDM EXTENSION ENDS
{
	TimerManager::getInstance()->addListener(this);
	socket->addListener(this);
};

Client::~Client() throw() {
	// CDM EXTENSION BEGINS
	stopChecking();
	// CDM EXTENSION ENDS
	TimerManager::getInstance()->removeListener(this);
	socket->removeListener(this);
	removeListeners();

	clearUsers();
	updateCounts(true);

	BufferedSocket::putSocket(socket);
	socket = NULL;
};

void Client::updateCounts(bool aRemove) {
	// We always remove the count and then add the correct one if requested...

	if(countType == COUNT_NORMAL) {
		Thread::safeDec(counts.normal);
	} else if(countType == COUNT_REGISTERED) {
		Thread::safeDec(counts.registered);
	} else if(countType == COUNT_OP) {
		Thread::safeDec(counts.op);
	}
	countType = COUNT_UNCOUNTED;

	string lastRegStatus = getRegStatus();
	if (getOp()) {
		setRegStatus("Op");
	} else if(registered) {
		setRegStatus("Registered");
	} else {
		setRegStatus("Un-Registered");
	}
	if (lastRegStatus != getRegStatus()) {
		FavoriteHubEntry::List& fl = HubManager::getInstance()->getFavoriteHubs();
		for(FavoriteHubEntry::List::const_iterator i = fl.begin(); i != fl.end(); ++i) {
			if((*i)->getServer().compare(getAddressPort()) == 0) {
				(*i)->setRegStatus(getRegStatus());
			}
		}
	}

	if(!aRemove) {
		if(getOp()) {
			Thread::safeInc(counts.op);
			countType = COUNT_OP;
		} else if(registered) {
			Thread::safeInc(counts.registered);
			countType = COUNT_REGISTERED;
		} else {
			Thread::safeInc(counts.normal);
			countType = COUNT_NORMAL;
		}
	}
}

void Client::connect(const string& aAddressPort) {
	addressPort = aAddressPort;
	string tmp;
	port = 411;
	Util::decodeUrl(aAddressPort, address, port, tmp);

	connect();
}

void Client::connect() {
	registered = false;
	reconnect = true;
	firstHello = true;
	supportFlags = 0;

	if(socket->isConnected()) {
		disconnect();
	}

	state = STATE_LOCK;

	socket->connect(address, port);
}

void Client::refreshUserList(bool unknownOnly /* = false */) {
	Lock l(cs);
	if(unknownOnly) {
		for(User::NickIter i = users.begin(); i != users.end(); ++i) {
			if(i->second->getConnection().empty()) {
				getInfo(i->second);
			}
		}
	} else {
		clearUsers();
		getNickList();
	}
}

void Client::clearUsers() {
	for(User::NickIter i = users.begin(); i != users.end(); ++i) {
		ClientManager::getInstance()->putUserOffline(i->second);		
	}
	users.clear();
}

void Client::onLine(const string& aLine) throw() {
	//dcdebug("Client::onLine %s\n", aLine);
	lastActivity = GET_TICK();

	if(aLine.length() == 0)
		return;
	
	if(aLine[0] != '$') {
		// Check if we're being banned...
		if(state != STATE_CONNECTED) {
			if(Util::findSubString(aLine, "banned") != string::npos) {
				reconnect = false;
			}
		}
		dcdebug("fire(ClientListener::MESSAGE %s\n", aLine);
		// CDM EXTENSION BEGINS
		if((showIPs || BOOLSETTING(GET_USER_COUNTRY)) && aLine[0] == '<' && getOp()) {
			string::size_type x;
			if( (x = aLine.find('>')) > 1) {
				// a nick?
				User::Ptr u;
				{
					Lock l(cs);
					User::NickIter ni = users.find(aLine.substr(1, x-1));
					if(ni != users.end()) {
						u = ni->second;
						string ip = u->getHisIp();
						string cc = u->getCountryCode();
						if(!ip.empty()) {
							// Flow84 starts
							string prefixmessage = "";
							if (BOOLSETTING(GET_USER_COUNTRY) && cc != "")
								prefixmessage += "[ " + cc + " ]";
							if ((BOOLSETTING(GET_USER_COUNTRY) && cc != "") && (showIPs && ip != ""))
								prefixmessage += " ";
							if (showIPs && ip != "")
								prefixmessage += "[ " + ip + " ]";
							if ((showIPs && ip != "") || (BOOLSETTING(GET_USER_COUNTRY) && cc != ""))
								prefixmessage += " ";
							// pothead change for svn62 (added spaces around ip)
							fire(ClientListener::Message(), this, Util::validateMessage(prefixmessage + aLine, true));
							// pothead end
							return;
						}
					}
				}
			}
		}
		// CDM EXTENSION ENDS
		fire(ClientListener::Message(), this, Util::validateMessage(aLine, true));
		return;
	}

	string cmd;
	string param;
	string::size_type x;
	
	if( (x = aLine.find(' ')) == string::npos) {
		cmd = aLine;
	} else {
		cmd = aLine.substr(0, x);
		param = aLine.substr(x+1);
	}

	if(cmd == "$Search") {
		if(state != STATE_CONNECTED) {
			return;
		}
		//Sulan start
		if(getHideShare()){
			return;
		}
		//End
		string::size_type i = 0;
		string::size_type j = param.find(' ', i);
		if(j == string::npos || i == j)
			return;
		string seeker = param.substr(i, j-i);
		i = j + 1;
		
		{
			Lock l(cs);
			u_int32_t tick = GET_TICK();

			seekers.push_back(make_pair(seeker, tick));

			// First, check if it's a flooder
			FloodIter fi;
			for(fi = flooders.begin(); fi != flooders.end(); ++fi) {
				if(fi->first == seeker) {
					return;
				}
			}

			int count = 0;
			for(fi = seekers.begin(); fi != seekers.end(); ++fi) {
				if(fi->first == seeker)
					count++;

				if(count > 7) {
					if(seeker.find("Hub:") != string::npos)
						fire(ClientListener::SearchFlood(), this, seeker.substr(4));
					else
						fire(ClientListener::SearchFlood(), this, seeker + STRING(NICK_UNKNOWN));

					flooders.push_back(make_pair(seeker, tick));
					return;
				}
			}
		}

		int a;
		if(param[i] == 'F') {
			a = SearchManager::SIZE_DONTCARE;
		} else if(param[i+2] == 'F') {
			a = SearchManager::SIZE_ATLEAST;
		} else {
			a = SearchManager::SIZE_ATMOST;
		}
		i += 4;
		j = param.find('?', i);
		if(j == string::npos || i == j)
			return;
		string size = param.substr(i, j-i);
		i = j + 1;
		j = param.find('?', i);
		if(j == string::npos || i == j)
			return;
		int type = Util::toInt(param.substr(i, j-i)) - 1;
		i = j + 1;
		param = param.substr(i);

		if(param.size() > 0) {	

			fire(ClientListener::Search(), this, seeker, a, size, type, param);
			
			if(seeker.find("Hub:") != string::npos) {
				User::Ptr u;
				{
					Lock l(cs);
					User::NickIter ni = users.find(seeker.substr(4));
					if(ni != users.end() && !ni->second->isSet(User::PASSIVE)) {
						u = ni->second;
						// CDM EXTENSION BEGINS
						//u->setFlag(User::PASSIVE);
						u->setPassive();
						// CDM EXTENSION ENDS
					}
				}

				if(u) {
					updated(u);
				}
			}
		}
	} else if(cmd == "$MyINFO") {
		string::size_type i, j;
		i = 5;
		string nick;
		j = param.find(' ', i);
		if( (j == string::npos) || (j == i) )
			return;
		nick = param.substr(i, j-i);
		i = j + 1;
		User::Ptr u;
		dcassert(nick.size() > 0);

		{
			Lock l(cs);
			User::NickIter ni = users.find(nick);
			if(ni == users.end()) {
				u = users[nick] = ClientManager::getInstance()->getUser(nick, this);
			} else {
				u = ni->second;
			}
		}
		j = param.find('$', i);
		if(j == string::npos)
			return;
		string tmpDesc = Util::validateMessage(param.substr(i, j-i), true);
		// Look for a tag...
		if(tmpDesc.size() > 0 && tmpDesc[tmpDesc.size()-1] == '>') {
			x = tmpDesc.rfind('<');
			if(x != string::npos) {
				// Hm, we have something...
				u->setTag(tmpDesc.substr(x));
				tmpDesc.erase(x);
			} else {
				u->setTag(Util::emptyString);
			}
		} else {
			u->setTag(Util::emptyString);
		}
		u->setDescription(tmpDesc);
		i = j + 3;
		j = param.find('$', i);
		if(j == string::npos)
			return;
		// CDM EXTENSION BEGINS
		char icon = param[j-1];
		u->setIconStatus(icon);
		// CDM EXTENSION ENDS
		u->setConnection(param.substr(i, j-i-1));
		i = j + 1;
		j = param.find('$', i);
		if(j == string::npos)
			return;
		u->setEmail(Util::validateMessage(param.substr(i, j-i), true));
		i = j + 1;
		j = param.find('$', i);
		if(j == string::npos)
			return;
		u->setBytesShared(param.substr(i, j-i));
		// CDM EXTENSION BEGINS
		if (getCheckFakeShare()) {
			int64_t byteshared = u->getBytesShared();
			string shared = Util::toString(byteshared);
			bool userFake = false;
			string fakeShareCheat;
			{
				Lock l(cs);
				FakeShares::List& ul = ClientProfileManager::getInstance()->getFakeShares();
				for(FakeShares::Iter j = ul.begin(); j != ul.end(); ++j) {
					if ( (j->getType()).compare("Exact") == 0 ) {
						if ( (j->getValue()).compare(shared) == 0 )
							userFake = true;
					} else if ( (j->getType()).compare("Contains") == 0 ) {
						if ( shared.find(j->getValue()) != string::npos )
							userFake = true;
					}
				}
			}
			if (userFake) {
				fakeShareCheat = STRING(FAKE_SHARE_OF)+ " " + shared + " B.";
				u->displayCheat(u->getNick() + " " + fakeShareCheat);
				u->setCheat(STRING(FAKE_SHARE) + " - " + fakeShareCheat, false, true);
				if (SETTING(FAKE_RAW))
					u->sendRawCommand(SETTING(FAKE_RAW), CONTEXT_RAW_FAKE_SHARE);
			}
			bool divisibleByOneMB = ( byteshared % (1024*1024) == 0 && byteshared != 0);
			if (divisibleByOneMB) {
				fakeShareCheat = shared + " is divisible by 1MB";
				if (BOOLSETTING(DISPLAY_DIVISIBLE_BY_1MB_CHEAT))
					u->displayCheat(u->getNick() + " " + fakeShareCheat);
				u->setCheat(STRING(FAKE_SHARE) + " - " + fakeShareCheat, false, true);
				if (SETTING(DIVISIBLE_BY_1MB_RAW))
					u->sendRawCommand(SETTING(DIVISIBLE_BY_1MB_RAW), CONTEXT_RAW_FAKE_SHARE);
			}
			// Mickey code starts
			bool sameNumbersMixed = false;
			int sharedLength = static_cast<int>(shared.length());
			int digitCount[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // array for counting digits 0, 1, 2, 3...9 in bytesShared

			// Counting numbers and adding the results to digitcount array.
			for (int i = 0; i < sharedLength; i++) {
				++digitCount[shared[i] - '0'];
			}
			// Getting the biggest number from array
			int max = 0;
			for (int i = 0; i < 10; i++) {
				if ( digitCount[i] > max ) {
					max = digitCount[i];
				}
			}
			// If a digit appears more than 6 times in share like: 95557555 then it's fake
			// 2*bytesShared.length() < 3*max
			if ((max >= 6) && (2 * sharedLength < 3 * max)) {
				sameNumbersMixed = true;
			}
			// Mickey code ends
			if (sameNumbersMixed) {
				fakeShareCheat = shared + " contains too many occurances of the same digit";
				if (BOOLSETTING(DISPLAY_TOO_MANY_OF_SAME_NUMBERS_CHEAT))
					u->displayCheat(u->getNick() + " " + fakeShareCheat);
				u->setCheat(STRING(FAKE_SHARE) + " - " + fakeShareCheat, false, true);
				if (SETTING(TOO_MANY_OF_SAME_NUMBERS_RAW))
					u->sendRawCommand(SETTING(TOO_MANY_OF_SAME_NUMBERS_RAW), CONTEXT_RAW_FAKE_SHARE);
			}
		}
		// potheads starts svn189
		u->rmDCDetect();
		// pothead ends

		if(u->isMyInfoSpamming()) {
			if(SETTING(FLOOD_NOTIFY)) {
				addLine("*** " + STRING(MYINFO_SPAM_DETECTED) + ": " + u->getNick());
			}
			if(SETTING(FLOOD_KICK) && getOp() && !u->isProtected()) {
				kick(u, "MyINFO spam");
			}
		}
		
		if(sendUserIp && u->getUserIp().empty())
			userIp(u); 
		// CDM EXTENSION ENDS
		fire(ClientListener::UserUpdated(), this, u);
	} else if(cmd == "$Quit") {
		if(!param.empty()) {
			User::Ptr u;
			{
				Lock l(cs);
				User::NickIter i = users.find(param);
				if(i == users.end()) {
					dcdebug("C::onLine Quitting user %s not found\n", param.c_str());
					return;
				}
				
				u = i->second;
				users.erase(i);
			}
			
			fire(ClientListener::Quit(), this, u);
			ClientManager::getInstance()->putUserOffline(u, true);
		}
	} else if(cmd == "$ConnectToMe") {
		if(state != STATE_CONNECTED) {
			return;
		}
		string::size_type i = param.find(' ');
		string::size_type j;
		if( (i == string::npos) || ((i + 1) >= param.size()) ) {
			return;
		}
		i++;
		j = param.find(':', i);
		if(j == string::npos) {
			return;
		}
		string server = param.substr(i, j-i);
		if(j+1 >= param.size()) {
			return;
		}
		fire(ClientListener::ConnectToMe(), this, server, param.substr(j+1));

	} else if(cmd == "$RevConnectToMe") {
		if(state != STATE_CONNECTED) {
			return;
		}
		User::Ptr u;
		bool up = false;
		{
			Lock l(cs);
			string::size_type j = param.find(' ');
			if(j == string::npos) {
				return;
			}

			User::NickIter i = users.find(param.substr(0, j));
			if(i == users.end()) {
				return;
			}

			u = i->second;
			if(!u->isSet(User::PASSIVE)) {
				// CDM EXTENSION BEGINS
				//u->setFlag(User::PASSIVE);
				u->setPassive();
				// CDM EXTENSION ENDS
				up = true;
			}
		}

		if(u) {
			if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
				fire(ClientListener::RevConnectToMe(), this, u);
			} else {
				// Notify the user that we're passive too...
				if(up)
					revConnectToMe(u);
			}

			if(up)
				updated(u);
		}
	} else if(cmd == "$SR") {
		SearchManager::getInstance()->onSearchResult(aLine);
	} else if(cmd == "$HubName") {
		name = param;
		fire(ClientListener::HubName(), this);
	} else if(cmd == "$Supports") {
		StringTokenizer<string> st(param, ' ');
		StringList& sl = st.getTokens();
		for(StringIter i = sl.begin(); i != sl.end(); ++i) {
			if(*i == "UserCommand") {
				supportFlags |= SUPPORTS_USERCOMMAND;
			} else if(*i == "NoGetINFO") {
				supportFlags |= SUPPORTS_NOGETINFO;
			} else if(*i == "UserIP2") {
				supportFlags |= SUPPORTS_USERIP2;
			}
		}
		fire(ClientListener::Supports(), this, sl);
	} else if(cmd == "$UserCommand") {
		string::size_type i = 0;
		string::size_type j = param.find(' ');
		if(j == string::npos)
			return;

		int type = Util::toInt(param.substr(0, j));
		i = j+1;
		if(type == UserCommand::TYPE_SEPARATOR) {
			int ctx = Util::toInt(param.substr(i));
			fire(ClientListener::UserCommand(), this, type, ctx, Util::emptyString, Util::emptyString);
		} else if(type == UserCommand::TYPE_RAW || type == UserCommand::TYPE_RAW_ONCE) {
			j = param.find(' ', i);
			if(j == string::npos)
				return;
			int ctx = Util::toInt(param.substr(i));
			i = j+1;
			j = param.find('$');
			if(j == string::npos)
				return;
			string name = param.substr(i, j-i);
			i = j+1;
			string command = param.substr(i, param.length() - i);
			fire(ClientListener::UserCommand(), this, type, ctx, Util::validateMessage(name, true, false), Util::validateMessage(command, true, false));
		}
	} else if(cmd == "$Lock") {
		if(state != STATE_LOCK) {
			return;
		}
		state = STATE_HELLO;

		if(!param.empty()) {
			string::size_type j = param.find(" Pk=");
			string lock, pk;
			if( j != string::npos ) {
				lock = param.substr(0, j);
				pk = param.substr(j + 4);
			} else {
				// Workaround for faulty linux hubs...
				j = param.find(" ");
				if(j != string::npos)
					lock = param.substr(0, j);
				else
					lock = param;
			}
			fire(ClientListener::CLock(), this, lock, pk);
		}
	} else if(cmd == "$Hello") {
		if(!param.empty()) {
			User::Ptr u = ClientManager::getInstance()->getUser(param, this);
			{
				Lock l(cs);
				users[param] = u;
			}

			if(getNick() == param){
				setMe(u);
				u->setFlag(User::DCPLUSPLUS);
				if(SETTING(CONNECTION_TYPE) != SettingsManager::CONNECTION_ACTIVE)
					u->setFlag(User::PASSIVE);


			}
			if(state == STATE_HELLO && getMe() == u) {
				state = STATE_CONNECTED;
				updateCounts(false);

				version(SETTING(CLIENTVERSION));
				getNickList();
				myInfo();
			}

			fire(ClientListener::UserUpdated(), this, u);
		}
	} else if(cmd == "$ForceMove") {
		disconnect();
		fire(ClientListener::Redirect(), this, param);
	} else if(cmd == "$HubIsFull") {
		fire(ClientListener::HubFull(), this);
	} else if(cmd == "$ValidateDenide") {		// Mind the spelling...
		disconnect();
		fire(ClientListener::ValidateDenied(), this);
	} else if(cmd == "$UserIP") {
		if(!param.empty()) {
			User::List v;
			StringTokenizer<string> t(param, "$$");
			StringList& l = t.getTokens();
			for(StringIter it = l.begin(); it != l.end(); ++it) {
				string::size_type j = 0;
				if((j = it->find(' ')) == string::npos)
					continue;
				if((j+1) == it->length())
					continue;
				// CDM EXTENSION BEGINS
				string nick = it->substr(0, j);
				v.push_back(ClientManager::getInstance()->getUser(nick, this));
				User::Ptr u;
				if (getOp()){
					{
						Lock l(cs);
						User::NickIter ni = users.find(nick);
						if(ni == users.end()) {
							u = users[nick] = ClientManager::getInstance()->getUser(nick, this);
						} else {
							u = ni->second;
						}
					}
				}
				//v.back()->setIp(it->substr(j+1));
				string IP = it->substr(j+1);
				v.back()->setUserIp(IP);
				ClientManager::getInstance()->setIPNick(IP, nick, true);
				User::updated(v.back());
				if(u && u->getCountryCodeCheck() && getOp()){}
				// CDM EXTENSION ENDS
			}
			fire(ClientListener::UserIp(), this, v);
		}
	} else if(cmd == "$NickList") {
		if(!param.empty()) {
			User::List v;
			StringTokenizer<string> t(param, "$$");
			StringList& sl = t.getTokens();

			for(StringIter it = sl.begin(); it != sl.end(); ++it) {
				v.push_back(ClientManager::getInstance()->getUser(*it, this));
			}

			{
				Lock l(cs);
				for(User::Iter it2 = v.begin(); it2 != v.end(); ++it2) {
					users[(*it2)->getNick()] = *it2;
				}
			}
			
			fire(ClientListener::UsersUpdated(), this, v);
		}
	} else if(cmd == "$OpList") {
		if(!param.empty()) {
			User::List v;
			StringTokenizer<string> t(param, "$$");
			StringList& sl = t.getTokens();
			for(StringIter it = sl.begin(); it != sl.end(); ++it) {
				v.push_back(ClientManager::getInstance()->getUser(*it, this));
				// CDM EXTENSION BEGINS
				//v.back()->setFlag(User::OP);
				v.back()->setOp();
				if(v.back() == getMe())
					if (checkOnConnect)
						startCheckOnConnect();
				// CDM EXTENSION ENDS
			}

			{
				Lock l(cs);
				for(User::Iter it2 = v.begin(); it2 != v.end(); ++it2) {
					users[(*it2)->getNick()] = *it2;
				}
			}
			fire(ClientListener::UsersUpdated(), this, v);
			updateCounts(false);
			// Special...to avoid op's complaining that their count is not correctly
			// updated when they log in (they'll be counted as registered first...)
			if(lastCounts != counts) {
				myInfo();
			}
		}
	} else if(cmd == "$To:") {
		string::size_type i = param.find("From:");
		if(i != string::npos) {
			i+=6;
			string::size_type j = param.find("$");
			if(j != string::npos) {
				string from = param.substr(i, j - 1 - i);
				if(from.size() > 0 && param.size() > (j + 1)) {
					fire(ClientListener::PrivateMessage(), this, ClientManager::getInstance()->getUser(from, this, false), Util::validateMessage(param.substr(j + 1), true));
				}
			}
		}
	} else if(cmd == "$GetPass") {
		registered = true;
		fire(ClientListener::GetPassword(), this);
	} else if(cmd == "$BadPass") {
		fire(ClientListener::BadPassword(), this);
	} else if(cmd == "$LogedIn") {
		fire(ClientListener::LoggedIn(), this);
	} else if(cmd == "$Z") { // If a ZLine command
		string::size_type i = x + 1, j = x + 1;
		bool corrupt = false;
		string rawParam;
		rawParam.reserve(aLine.size());

		// unescape \\ to \ and \P to |
		while((i = aLine.find("\\", i)) != string::npos) {
			if (i + 1 > aLine.size()) {
				corrupt = true;
				break;
			}
			rawParam += aLine.substr(j, i - j);
			switch (aLine[i+1]) {
				case '\\':
					rawParam += '\\';
					break;
				case 'P':
					rawParam += '|';
					break;
				default:
					corrupt = true;
					break;
			}
			i += 2;
			j = i;
		}
		rawParam += aLine.substr(j);

		if (!corrupt) {
			// unzip the ZBlock
			UnZFilter filter;
			string lines = "";
			bool more = true;
			string::size_type j, readFromPos = 0, estOutSize = rawParam.size() * 4;
			AutoArray<u_int8_t> temp(estOutSize);

			while (more) {
				j = estOutSize;
				try {
					more = filter(rawParam.c_str() + readFromPos, i, temp, j);
				} catch(...){
					dcdebug("Error during Zline decompression\n");
#if _SvnBuild
					LogManager::getInstance()->message("Error during Zline decompression");
#endif // _Beta
					break;
				}
				lines += string((char*)(u_int8_t*)temp, j);
				readFromPos += i;

				// split lines up into indiviual commands
				StringTokenizer<string> st(lines, '|'); 
 
				// if there is more data, keep the last token, it might not be complete
				if(more) {
					lines = st.getTokens().back();
					st.getTokens().pop_back();
				}

				for (StringList::iterator k = st.getTokens().begin(); k < st.getTokens().end(); k++) {
					// "fire" the lines, ignoring any included ZLines
						if ((*k).compare(0, 3, "$Z "))
						onLine(*k);
				}

				// a nmdc command over 1mb ?
				if (lines.size() > 1048576) {
					dcdebug("Malicious data found during ZLine decompression\n");
#if _SvnBuild
					LogManager::getInstance()->message("Malicious data found during ZLine decompression");
#endif // _Beta
					break;
				}
			}
		} else {
			dcdebug("Corrupt Zline datastream\n");
#if _SvnBuild
			LogManager::getInstance()->message("Corrupt Zline datastream");
#endif // _Beta
		}
	} else {
		dcassert(cmd[0] == '$');
		dcdebug("Client::onLine Unknown command %s\n", aLine.c_str());
	} 
}

string Client::getDescriptionFormat() const {
	return "%[d]%[p]<++ V:%[v],M:%[m],H:%[h],S:%[s]%[o]%[b]>";
}

void Client::myInfo() {
	checkstate();

	dcdebug("MyInfo %s...\n", getNick().c_str());
	lastCounts = counts;
	
	string tmp1 = ";**\x1fU9";
	string tmp2 = "+L9";
	string tmp3 = "+G9";
	string tmp4 = "+R9";
	string tmp5 = "+N9";
	string tmp6 = "+Q9";
	string tmp7 = ";CBCL\x1f";
	string::size_type i;
	
	for(i = 0; i < 6; i++) {
		tmp1[i]++; tmp7[i]++;
	}
	for(i = 0; i < 3; i++) {
		tmp2[i]++; tmp3[i]++; tmp4[i]++; tmp5[i]++; tmp6[i]++;
	}

#if _SvnBuild
	tmp7 = tmp7 + DISPLAYVERSIONSTRING + " svn" + SVN + ">"; 		
#else // _Svnbuild
	tmp7 = (BOOLSETTING(HIDE_DCDM_TAG) && getOp()) ? Util::emptyString : tmp7 + DISPLAYVERSIONSTRING + ">"; 
#endif // _SvnBuild

	char modeChar = '?';
	if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE)
		modeChar = 'A';
	else if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_PASSIVE)
		modeChar = 'P';
	else if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_SOCKS5)
		modeChar = '5';
	
	string uMin = (SETTING(MIN_UPLOAD_SPEED) == 0) ? Util::emptyString : tmp5 + Util::toString(SETTING(MIN_UPLOAD_SPEED));
	string uLim = BOOLSETTING(THROTTLE_ENABLE) && SETTING(MAX_UPLOAD_SPEED) ? (tmp6 + Util::toString(SETTING(DOWNLOAD_UPLOAD_RATIO))) : Util::emptyString;
	send("$MyINFO $ALL " + Util::validateNick(getNick()) + " " + Util::validateMessage(getDescription(), false) + tmp7 +
		tmp1 + VERSIONSTRING + tmp2 + modeChar + tmp3 + getCounts() + tmp4 + Util::toString(SETTING(SLOTS)) + uMin + uLim + ">$ $" + SETTING(CONNECTION) + "\x01$" + Util::validateMessage(SETTING(EMAIL), false) + '$' + 
		//Sulan start 
		// pothead add and fix #8
		(getHideShare() ? "0" : ShareManager::getInstance()->getShareSizeString() ) + "$|");
		//ShareManager::getInstance()->getShareSizeString() + "$|");
}

void Client::disconnect() throw() {	
	state = STATE_CONNECT;
	socket->disconnect();
	{ 
		Lock l(cs);
		clearUsers();
	}
	stopChecking();
}

void Client::search(int aSizeType, int64_t aSize, int aFileType, const string& aString){
	checkstate(); 
	char* buf;
	char c1 = (aSizeType == SearchManager::SIZE_DONTCARE) ? 'F' : 'T';
	char c2 = (aSizeType == SearchManager::SIZE_ATLEAST) ? 'F' : 'T';
	string tmp = aString;
	string::size_type i;
	while((i = tmp.find(' ')) != string::npos) {
		tmp[i] = '$';
	}
	int chars = 0;
	if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
		string x = getLocalSearchIp();
		buf = new char[x.length() + aString.length() + 64];
		chars = sprintf(buf, "$Search %s:%d %c?%c?%s?%d?%s|", x.c_str(), (SETTING(SEARCH_IN_PORT)?SETTING(SEARCH_IN_PORT):SETTING(IN_PORT)), c1, c2, Util::toString(aSize).c_str(), aFileType+1, tmp.c_str());
	} else {
		buf = new char[getNick().length() + aString.length() + 64];
		chars = sprintf(buf, "$Search Hub:%s %c?%c?%s?%d?%s|", getNick().c_str(), c1, c2, Util::toString(aSize).c_str(), aFileType+1, tmp.c_str());
	}
	send(buf, chars);
	delete[] buf;
}

void Client::kick(const User::Ptr& aUser, const string& aMsg) {
	checkstate(); 
	dcdebug("Client::kick\n");
	// CDM EXTENSION BEGINS
	string logMessage = aUser->getNick() + " " + STRING(KICKED_REASON) + " " + aMsg + " (" + getAddressPort() + ')';
	LOGDT("kicks", logMessage);
	// CDM EXTENSION ENDS
	static const char str[] = 
		"$To: %s From: %s $<%s> You are being kicked because: %s|<%s> %s is kicking %s because: %s|";
	string msg2 = Util::validateMessage(aMsg, false);
	
	char* tmp = new char[sizeof(str) + 2*aUser->getNick().length() + 2*msg2.length() + 4*getNick().length()];
	const char* u = aUser->getNick().c_str();
	const char* n = getNick().c_str();
	const char* m = msg2.c_str();
	sprintf(tmp, str, u, n, n, m, n, n, u, m);
	send(tmp);
	delete[] tmp;
	
	// Short, short break to allow the message to reach the client...
	Thread::sleep(200);
	send("$Kick " + aUser->getNick() + "|");
}

void Client::kick(User* aUser, const string& aMsg) {
	checkstate(); 
	dcdebug("Client::kick\n");
	// CDM EXTENSION BEGINS
	string logMessage = aUser->getNick() + " " + STRING(KICKED_REASON) + " " + aMsg + " (" + getAddressPort() + ')';
	LOGDT("kicks", logMessage);
	// CDM EXTENSION ENDS
	static const char str[] = 
		"$To: %s From: %s $<%s> You are being kicked because: %s|<%s> %s is kicking %s because: %s|";
	string msg2 = Util::validateMessage(aMsg, false);
	
	char* tmp = new char[sizeof(str) + 2*aUser->getNick().length() + 2*msg2.length() + 4*getNick().length()];
	const char* u = aUser->getNick().c_str();
	const char* n = getNick().c_str();
	const char* m = msg2.c_str();
	sprintf(tmp, str, u, n, n, m, n, n, u, m);
	send(tmp);
	delete[] tmp;
	
	// Short, short break to allow the message to reach the client...
	Thread::sleep(100);
	send("$Kick " + aUser->getNick() + "|");
}
//Sulan start svn109
string ClientScriptInstance::formatChatMessage(Client* aClient, string aLine, bool mainChat) {
//End
	//dcdebug("formatChatMessage: %s\n", aLine);
	Lock l(cs);
	//Sulan start svn109
	if(!mainChat && BOOLSETTING(USE_PM_FORMATTING_SCRIPT))
		MakeCall("dcpp", "FormatPmText", 1, aClient, aLine);
	else 
		MakeCall("dcpp", "FormatChatText", 1, aClient, aLine);
	//End

	if (lua_isstring(L, -1)) aLine = lua_tostring(L, -1);
		
	lua_settop(L, 0);
	return aLine;
}

bool ClientScriptInstance::onHubFrameEnter(Client* aClient, const string& aLine) {
	Lock l(cs);
	MakeCall("dcpp", "OnCommandEnter", 1, aClient, aLine);
	return GetLuaBool();
}

bool ClientScriptInstance::onClientMessage(Client* aClient, const string& aLine) {
	Lock l(cs);
	MakeCall("nmdch", "DataArrival", 1, aClient, aLine);
	return GetLuaBool();
}

bool ClientScriptInstance::clientInfo(Client* aClient, const string& aLine) {
	Lock l(cs);
	MakeCall("nmdch", "ClientInfo", 1, aClient, aLine);
	return GetLuaBool();
}

// TimerManagerListener
void Client::on(TimerManagerListener::Second, u_int32_t aTick) throw() {

		if(socket && (lastActivity + (120+Util::rand(0, 60)) * 1000) < aTick) {
			// Nothing's happened for ~120 seconds, check if we're connected, if not, try to connect...
			lastActivity = aTick;
			// Try to send something for the fun of it...
			if(isConnected()) {
				dcdebug("Testing writing...\n");
				socket->write("|", 1);
			} else {
				// Try to reconnect...
				if(reconnect && !address.empty()) {
					connect();
				}
			}
		}
		{
			Lock l(cs);
			
			while(!seekers.empty() && seekers.front().second + (5 * 1000) < aTick) {
				seekers.pop_front();
			}
			
			while(!flooders.empty() && flooders.front().second + (120 * 1000) < aTick) {
				flooders.pop_front();
			}
		}
}

// BufferedSocketListener
void Client::on(BufferedSocketListener::Line, const string& aLine) throw() {
		if (onClientMessage(this, Util::validateMessage(aLine, true)))
			return;
		// CDM EXTENSION BEGINS
		COMMAND_DEBUG(aLine, DebugManager::HUB_IN, getIpPort());
		// CDM EXTENSION ENDS
		onLine(aLine);
}
void Client::on(BufferedSocketListener::Failed, const string& aLine) throw() {
		{
			Lock l(cs);
			clearUsers();
		}
		if(state == STATE_CONNECTED)
			state = STATE_CONNECT;
		fire(ClientListener::Failed(), this, aLine);
}
void Client::on(BufferedSocketListener::Connected) throw() {
	lastActivity = GET_TICK();
	fire(ClientListener::Connected(), this);
}
// CDM EXTENSION BEGINS
string Client::getNickFromUserIp(const string& aUserIp) throw() {
	{
		Lock l(cs);
		for(User::NickIter i = users.begin(); i != users.end(); ++i) {
			if(i->second->getUserIp().compare(aUserIp) == 0) {
				return i->second->getNick();
			}
		}
	}
	return Util::emptyString;
}

void Client::ThreadedCheck::startCheck(Client* c) {
	client = c;
	start();
}

int Client::ThreadedCheck::run() {
	setThreadPriority(Thread::LOW);
	inThread = true;
	if(checkOnConnect && !keepChecking) { 
		sleep(SETTING(CHECK_DELAY)); 
		keepChecking = true;
	}
	bool checkClients = client->getCheckClients();
	bool checkFilelists = client->getCheckFilelists();
	if(client->state != STATE_CONNECTED || (!checkClients && !checkFilelists)) { keepChecking = false; }
	bool extendedCheck1 = BOOLSETTING(EXTENDED_CHECKING);
	bool extendedCheck2 = BOOLSETTING(EXTENDED_CHECKING2);
	bool fileListCheck, iterBreak;
	initialSweepComplete = !checkClients || !BOOLSETTING(CHECK_ALL_CLIENTS_BEFORE_FILELISTS);
	u_int8_t secs = 0;
	int sleepTime = SETTING(SLEEP_TIME);

	while(keepChecking) {
		if(client->state == STATE_CONNECTED) {
			int t = 0;
			int f = 0;
			QueueItem::StringMap& q = QueueManager::getInstance()->lockQueue();
			for(QueueItem::StringIter i = q.begin(); i != q.end(); ++i) {
				if( i->second->isSet(QueueItem::FLAG_TESTSUR) || i->second->isSet(QueueItem::FLAG_TESTSUR_O_NICK) || i->second->isSet(QueueItem::FLAG_TESTSUR_R_NICK)) {
					t++;
				} else if (i->second->isSet(QueueItem::FLAG_CHECK_FILE_LIST)) {
					f++;
				}
			}
			QueueManager::getInstance()->unlockQueue();
			//q.clear();
			fileListCheck = f < SETTING(MAX_FILELISTS) && checkFilelists;
			if(!checkClients || t < SETTING(MAX_TESTSURS)) {
				Lock l(client->cs);
				iterBreak = false;
				for(User::NickIter i = client->users.begin(); i != client->users.end(); ++i) {
					if(client->state != STATE_CONNECTED) { break; }
					if( i->second->isCheckable() ) {
						if (checkClients) {
							if (i->second->shouldTestSUR() ) {
								try {
									QueueManager::getInstance()->addTestSUR(i->second);
									i->second->setHasTestSURinQueue(true);
									if(BOOLSETTING(SEND_CONNECT_TO_ME_AFTER_ADDING_TESTSUR))
										i->second->connect();
									iterBreak = true;
									break;
								} catch(...) {
									dcdebug("Exception adding testsur: %s\n", i->second->getNick());
								}
							}
	
							if (extendedCheck1 && i->second->shouldTestSURoNick()) {
								try {
									QueueManager::getInstance()->addTestSUR(i->second, QueueManager::O_NICK);
									i->second->setHasTestSURinQueue(true);
									if(BOOLSETTING(SEND_CONNECT_TO_ME_AFTER_ADDING_TESTSUR))
										i->second->connect();
									iterBreak = true;
									break;
								} catch(...) {
									dcdebug("Exception adding testsur: %s\n", i->second->getNick());
								}
							} 
							if (extendedCheck2 && i->second->shouldTestSURrNick()) {
								try {
								QueueManager::getInstance()->addTestSUR(i->second, QueueManager::R_NICK);
								i->second->setHasTestSURinQueue(true);
								if(BOOLSETTING(SEND_CONNECT_TO_ME_AFTER_ADDING_TESTSUR))
									i->second->connect();
								iterBreak = true;
								break;
								} catch(...) {
									dcdebug("Exception adding testsur: %s\n", i->second->getNick());
								}
							}
						}
						// pothead starts svn183
						if 	(i->second->isSet(User::FAILED_ADLSEARCH) && (SETTING(ADL_FAIL_HOURS) > 0) && (GET_TICK() > (i->second->getTimeForFilelistRecheck() + SETTING(ADL_FAIL_HOURS) * 3600 * 1000))) {
							i->second->setShouldRecheckFilelistBecauseOfADLSearch();
						}
						if ((initialSweepComplete && fileListCheck && checkFilelists) || i->second->getShouldRecheckFilelistBecauseOfADLSearch()) {
						// pothead ends
							if (i->second->shouldCheckFilelist(checkClients, extendedCheck1, extendedCheck2)) {
								try {
									QueueManager::getInstance()->addList(i->second, QueueItem::FLAG_CHECK_FILE_LIST, Util::emptyString, true);
									if(BOOLSETTING(SEND_CONNECT_TO_ME_AFTER_ADDING_TESTSUR))
										i->second->connect();
									i->second->setHasFilelistinQueue(true);
									break;
								} catch(...) {
									dcdebug("Exception adding filelist %s\n", i->second->getNick());
								}
							}
						}
						//DETECTION_DEBUG("Skipping user " + i->second->getNick())
					}
				}
				if(!initialSweepComplete) {
					initialSweepComplete = !iterBreak;
				}
			}
			if(secs >= 30) {
				try {
					QueueManager::getInstance()->removeOfflineChecks();
					secs = 0;
				} catch(...) {
					// oh well, try again in 10 secs.
					secs = 20;
				}
			} else {
				secs++;
			}
			sleep(sleepTime);
		} else {
			sleep(10000);
		}
	}
	inThread = false;
	return 0;
}

void Client::send(const string& a) throw() {
	lastActivity = GET_TICK();
	//dcdebug("Sending %d to %s: %.40s\n", a.size(), getName().c_str(), a.c_str());
	// CDM EXTENSION BEGINS
	COMMAND_DEBUG(a, DebugManager::HUB_OUT, getIpPort());
	// CDM EXTENSION ENDS
	socket->write(a);
}

void Client::send(const char* aBuf, int aLen) throw() {
	
	lastActivity = GET_TICK();
	// CDM EXTENSION BEGINS
	string mess(aBuf);
	COMMAND_DEBUG(mess, DebugManager::HUB_OUT, getIpPort());
	// CDM EXTENSION ENDS
	socket->write(aBuf, aLen);
}

string Client::getSpeedStats() {
	Lock l(cs);
	string report = "\nSlowest known uploaders:\n";
	StringIntMap speedmap;
	// fill the speedmap...
	for(User::NickIter i = users.begin(); i != users.end(); ++i) {
		if(i->second->getDownloadSpeed() != -1) {
			speedmap.insert(make_pair(i->second->getDownloadSpeed(), "\n" + i->second->getNick() + ":\t" + Util::formatBytes(i->second->getDownloadSpeed()) + "/s" + "\t " + STRING(HAS) + " " + STRING(SLOTS) + ": " + i->second->getTagSlots() + "\t" + i->second->getConnection()));
		}
	}

	// get the slowest
	int results = 0;
	for(StringIntIter j = speedmap.begin(); j != speedmap.end(); ++j) {
		//report += '\n' + j->second + ":\t" + Util::formatBytes(j->first) + "/s" + "\t " + STRING(HAS) + " " + STRING(SLOTS) + ": " + j->getTagSlots() + "\t" + j->getConnection();
		report += j->second;
		if(++results > 9) break;
	}

	// get the fastest
	report += "\n\nFastest known uploaders:\n";
	results = 0;
	for(RevSpeedIter k = speedmap.rbegin(); k != speedmap.rend(); ++k) {
		//report += '\n' + k->second + ":\t" + Util::formatBytes(k->first) + "/s" + "\t " + STRING(HAS) + " " + STRING(SLOTS) + k->second->getTagSlots() + "\t" + k->second->getConnection();
		report += k->second;
		if(++results > 9) break;
	}

	return report;
}
//Sulan
void Client::reloadFavHubs(){
	FavoriteHubEntry::List& fl = HubManager::getInstance()->getFavoriteHubs();
		for(FavoriteHubEntry::List::const_iterator i = fl.begin(); i != fl.end(); ++i) {
			if((*i)->getServer().compare(getAddressPort()) == 0) {
				//Add to favs
				setRawOne((*i)->getRawOne());
				setRawTwo((*i)->getRawTwo());
				setRawThree((*i)->getRawThree());
				setRawFour((*i)->getRawFour());
				setRawFive((*i)->getRawFive());
				setRawSix((*i)->getRawSix());
				setRawSeven((*i)->getRawSeven());
				setRawEight((*i)->getRawEight());
				setRawNine((*i)->getRawNine());
				setRawTen((*i)->getRawTen());
				setOpChat((*i)->getOpChat());
				setSendUserIp((*i)->getUserIp());
				setCheckClients((*i)->getCheckClients());
				setCheckFilelists((*i)->getCheckFilelists());
				setCheckFakeShare((*i)->getCheckFakeShare());
				//Gabberworld start
				setCountryCodeCheck((*i)->getCountryCodeCheck());
				setCountryCodeRaw((*i)->getCountryCodeRaw());
				//Gabberworld end
				setProtectedUsers((*i)->getProtectedUsers());
				setHideShare((*i)->getHideShare());
				//Sulan start 28
				setTrigger((*i)->getTrigger());
				//End 28
				// pothead starts svn192 svn196 (sulan code)
				setHubMessage((*i)->getHubMessage());
				setUseMyinfoDetect((*i)->getUseMyinfoDetect());
				//Sulan start svn?
				setDisableSearchSelect((*i)->getDisableSearchSelect());
				setDisableRaws((*i)->getDisableRaws());
				// pothead ends
				myInfo();
				return;					
			}
		}
}
//Sulan start svn 168 , // Pothead starts (aHubCount and forFilelist) svn182
User::Ptr Client::findUser(int64_t aShare, const string& aIp, const string& aEmail, const string& aConnection, const string& aHubCount, bool forFilelist){
	//We dont need to check bots or people that isn't proporly logged in
	//Neither can we check people in hubs that doesn't send complete MyINFO (Except if it's a filelist)
	if (forFilelist) {
		for(User::NickIter i = users.begin(); i != users.end(); ++i) {
			if((i->second->getHisIp().compare(aIp) == 0 || i->second->getHisIp().empty() || aIp.empty()) && (aShare == i->second->getBytesShared() || aShare == 0 || i->second->getBytesShared() == 0) && (i->second->getEmail().compare(aEmail) == 0 || i->second->getEmail().empty() || aEmail.empty()) && (i->second->getConnection().compare(aConnection) == 0) && (i->second->getHubs().compare(aHubCount) == 0 || i->second->getConnection().empty() || aHubCount.empty())){ 
#if _SvnBuild
				LogManager::getInstance()->message(aIp + " matched to " + i->second->getHisIp() + " with nick " + i->second->getNick() + " in this hub " + getAddressPort() + " where you " + (getHideShare() ? "don't " : "") + "share in");
#endif // _Beta
				return i->second; 
			} //Return this one, it appears to be the same.
		}
	} else {
		if(aConnection.empty()) return NULL; 

		for(User::NickIter i = users.begin(); i != users.end(); ++i) {
			if(i->second->getHisIp().compare(aIp) == 0 && (aShare == i->second->getBytesShared() || aShare == 0 || i->second->getBytesShared() == 0) && i->second->getEmail().compare(aEmail) == 0 && i->second->getConnection().compare(aConnection) == 0 && i->second->getHubs().compare(aHubCount) == 0){ 
				return i->second; 
			} //Return this one, it appears to be the same.
		}
	}
	// pothead ends
	return NULL;
}
//End


string Client::findNickIp(const string& aHint, bool aIsIp){
	string ret;
	dcdebug("Client::findNickIp hint: %s\n", aHint.c_str());
	for(User::NickIter i = users.begin(); i != users.end(); ++i) {
		if(aIsIp && Util::stricmp(aHint, i->second->getHisIp().c_str()) !=0 )
			continue;
		else if( !aIsIp && Util::stricmp(aHint, i->second->getNick().c_str()) !=0 )
			continue;
		ret += "[> ";
		ret += i->second->getNick();
		ret += "\t[ ";
		ret += i->second->getHisIp();
		ret += " ]\t[";
		ret += i->second->getClientAddressPort();
		ret += "]\r\n";
	}
	return ret;
}

int Client::countExistingIps(const string& aIP)
{
	int matches = 0;
	Lock l(cs);
	for(User::NickIter i = users.begin(); i != users.end(); ++i) {
		if(Util::stricmp(aIP, i->second->getHisIp()) == 0)
			++matches;
	}
	return matches;
}

bool Client::getDisableRaws(const int context) {
	int finder;
	switch (context) {
		default : finder = 0; break;
		case 2 : finder = 1; break;
		case 4 : finder = 2; break;
		case 8 : finder = 3; break;
		case 16 : finder = 4; break;
		case 32: finder = 5; break;
		case 64 : finder = 6; break;
		case 128 : finder = 7; break;
		case 256 : finder = 8; break;
		case 512: finder = 9; break;
		case 1024 : finder = 10; break;
	}

	if (finder == 10) {
		return false;
	} else {
		return Util::toBool(Util::getTokenAt(getDisableRaws(), ';', finder));
	}
}
// CDM EXTENSION ENDS
/**
 * @file
 * $Id: Client.cpp Last changed: 2005-07-14 13:05:46 By: sulan for SVN: 247 Exp$
 */
